package top.lshaci.learning.framework.swagger.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Service;
import top.lshaci.framework.utils.FreemarkerUtil;
import top.lshaci.learning.framework.swagger.model.Field;
import top.lshaci.learning.framework.swagger.model.Model;
import top.lshaci.learning.framework.swagger.model.Parameter;
import top.lshaci.learning.framework.swagger.model.Path;
import top.lshaci.learning.framework.swagger.service.Swagger2DocService;

import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Swagger2DocServiceImpl
 *
 * @author lshaci
 * @since 1.0.0
 */
@Service
public class Swagger2DocServiceImpl implements Swagger2DocService {

    @Override
    public void generator(String url, HttpServletResponse response) {
        String resp = HttpUtil.get(url);
        JSONObject respJson = JSON.parseObject(resp);
        List<Model> models = definitions(respJson.getJSONObject("definitions"));

        List<Path> paths = paths(respJson.getJSONObject("paths"));

        Map<String, Object> model = new HashMap<>();
        model.put("models", models);
        model.put("paths", paths);

        try {
            response.setHeader("Content-disposition", "attachment; filename=swagger.doc");
            FreemarkerUtil.generate("swagger.ftl", model, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List<Path> paths(JSONObject paths) {
        return paths.keySet().stream().flatMap(url -> {
            JSONObject value = paths.getJSONObject(url);
            return value.keySet().stream().map(method -> {
                JSONObject req = value.getJSONObject(method);
                Path path = new Path();
                path.setUrl(url);
                path.setMethod(method);
                path.setName(req.getString("summary"));
                JSONArray consumes = req.getJSONArray("consumes");
                if (CollectionUtil.isNotEmpty(consumes)) {
                    path.setConsumes(consumes.stream().map(Object::toString).collect(Collectors.joining(", ")));
                }
                List<Parameter> parameters = parameters(req.getJSONArray("parameters"));
                Field response = responses(req.getJSONObject("responses"));
                path.setParameters(parameters);
                path.setResponse(response);
                return path;
            });
        }).collect(Collectors.toList());
    }

    private Field responses(JSONObject responses) {
        Field field = new Field();
        JSONObject success = responses.getJSONObject("200");
        handleResponse(success, field);
        return field;
    }

    private void handleResponse(JSONObject success, Field field) {
        handleSchema(success, field);
        String format = field.getFormat();
        if (StrUtil.isBlank(format)) {
            field.setType("void");
            return;
        }
        String[] formats = format.replaceAll("«|»", "_").split("_");
        String description = Arrays.stream(formats).reduce((f, s) -> s).get();
        field.setDescription(description);
    }

    private List<Parameter> parameters(JSONArray parameters) {
        if (CollectionUtil.isEmpty(parameters)) {
            return null;
        }

        return parameters.stream().map(p -> {
            JSONObject jsonObject = (JSONObject) p;
            Parameter parameter = new Parameter();
            parameter.setIn(jsonObject.getString("in"));
            parameter.setName(jsonObject.getString("name"));
            parameter.setType(jsonObject.getString("type"));
            parameter.setRequire(jsonObject.getBoolean("required"));
            parameter.setDescription(jsonObject.getString("description"));

            handleSchema(jsonObject, parameter);
            return parameter;
        }).collect(Collectors.toList());
    }

    private void handleSchema(JSONObject jsonObject, Field field) {
        JSONObject schema = jsonObject.getJSONObject("schema");
        if (Objects.isNull(schema)) {
            return;
        }

        handleItems(schema, field);
    }

    public List<Model> definitions(JSONObject definitions) {
        return definitions.keySet().stream().map(k -> {
            JSONObject value = definitions.getJSONObject(k);
            JSONObject properties = value.getJSONObject("properties");
            JSONArray required = value.getJSONArray("required");
            List<String> requiredField = new ArrayList<>();
            if (CollectionUtil.isNotEmpty(required)) {
                requiredField = value.getJSONArray("required").stream().map(Object::toString).collect(Collectors.toList());
            }

            List<Field> fields = properties2Model(properties, requiredField);
            Model model = new Model();
            model.setTitle(k);
            model.setType(value.getString("type"));
            model.setFields(fields);
            return model;
        }).collect(Collectors.toList());
    }

    public List<Field> properties2Model(JSONObject properties, List<String> required) {
        if (properties == null) {
            return Collections.emptyList();
        }
        return properties.keySet().stream().map(k -> {
            JSONObject value = properties.getJSONObject(k);
            Field field = new Field();
            field.setName(k);
            field.setType(value.getString("type"));
            field.setFormat(value.getString("format"));
            field.setRequire(required.contains(k));
            field.setDescription(value.getString("description"));
            handleItems(value, field);

            return field;
        }).collect(Collectors.toList());
    }

    private void handleItems(JSONObject value, Field field) {
        JSONObject items = value.getJSONObject("items");
        String ref = value.getString("$ref");
        if (Objects.isNull(items) && StrUtil.isBlank(ref)) {
            return;
        }

        if (StrUtil.isBlank(ref)) {
            ref = items.getString("$ref");
        }
        String format = "";
        if (StrUtil.isBlank(ref)) {
            String type = items.getString("type");
            format = StrUtil.isNotBlank(type) ? type : "";
        } else {
            field.setRef(true);
            format = Arrays.stream(ref.split("/")).reduce((f, s) -> s).get();
        }

        field.setFormat(format);
    }
}
